﻿#include "ui/ui.h"
#include "ui/control_base.h"
#include "ui/animation.h"
#include "shape.h"
#include "shader.h"
#include "context.h"
#include "target.h"
#include "shared.h"
#include "draw.h"
#include <iostream>

using namespace std;
using namespace anip;

ui::Control* ui::Control::_TopLevel = nullptr;
ui::Control* ui::Control::_DraggingControl = nullptr;
float ui::Control::_UIScaling = 1;

//bool ui::UseDebug = false;

ui::Flow::Flow(const Container& c) :
	containerWidth(c.size().x), 
	containerHeight(c.size().y), 
	columnFactor(c.flowColumnFactor()),
	dockableStart(), dockableEnd(c.size() - c.margin() - c.marginBottom()),
	borderLeft(r::Flow_hMargin),
	borderRight(c.size().x - r::Flow_hMargin),
	currentX(r::Flow_hMargin),
	currentY(r::Flow_vMargin)
{}

ui::Flow::Flow(invec2 size, invec2 marginSum, float fac) :
	containerHeight(size.x), containerWidth(size.y),
	columnFactor(fac),
	dockableStart(), dockableEnd(size - marginSum),
	borderLeft(r::Flow_hMargin),
	borderRight(size.x - r::Flow_hMargin),
	currentX(r::Flow_hMargin),
	currentY(r::Flow_vMargin)
{}

void ui::Flow::newLine()
{
	if (hasRowHeader)
		currentX = containerWidth * columnFactor + r::Flow_Separator;
	else
		currentX = borderLeft;
	currentY += lineHeight + r::Flow_LineSpacing;
	lineHeight = 0;
}

// CONTROL

void ui::Control::setUIScaling(float s)
{
	assert(s > 0);
	_UIScaling = s;
	if (_TopLevel != nullptr)
	{
		_TopLevel->setSize(Context::size() / s);
	}
}

ui::Control::Control(Container* parent, invec2 pos, invec2 size, const wstring& text)
{
	if (parent != nullptr) parent->addChild(*this);
	setPosition(pos);
	setSize(size);
	setText(text);

	scroll.registerListener(this,
		[&](bvec2& self)
		{
			postSignal({ Signal::Type::NeedsRedraw });
			ANIP_ui_LoopOverChildren(child)
				child->setPosition(child->_pos);

			// ANIP_UNDONE: I commented this out since it is totally wrong though we do 
			// need a way to 'refresh' mouse position when scrolling. Looks like it's
			// relatively simple to do this.
			
			// Edit: WHY did I think it is totally wrong?
			//processMouseMove(toLocalPos(Context::mousePos()) + _margin - scroll());
		});
}

ui::Control::~Control()
{
	Control* child = _firstChild;
	_firstChild = nullptr;
	_lastChild = nullptr;
	while (child != nullptr)
	{
		Control* next = child->_next;
		delete child;
		child = next;
	}
}

void anip::ui::Control::forceDehoverChildren()
{
	if (_hoveredChild != nullptr)
	{
		_hoveredChild->processMouseLeave();
		_hoveredChild = nullptr;
	}
}

void anip::ui::Control::forceDefocusChildren()
{
	if (_focusedChild != nullptr)
	{
		_focusedChild->processDefocus();
		_focusedChild = nullptr;
	}
}

bool ui::Control::isPointInside(invec2 m)
{
	return math::inRectangle(m, { 0, 0 }, size());
}

void ui::Control::processMouseMove(invec2 pos)
{
	if (_DraggingControl != nullptr)
	{
		if (_DraggingControl == this) return;
		_dragOver = true;

		_dragOverChild = nullptr;
		bool set = false;
		ANIP_ui_LoopOverChildren(child)
		{
			vec2 cpos = pos - child->_pos - _margin 
				+ (child->fixedPosition ? vec2{0} : scroll());
			if (!set && child != _DraggingControl && child->isPointInside(cpos))
			{
				_dragOverChild = child;
				child->_dragOver = true;
				child->processMouseMove(cpos);
				set = true;
			}
			else
				child->_dragOver = false;
		}
		return;
	}

	_dragOver = false;
	_dragOverChild = nullptr;

	if (onMouseMove) onMouseMove(*this, pos);

	if (_focusedChild != nullptr && _focusedChild->_dragPossible && !_focusedChild->_dragging)
	{
		vec2 cpos = pos - _focusedChild->_pos - _margin
			+ (_focusedChild->fixedPosition ? vec2{0} : scroll());
		if (_DraggingControl == nullptr && _focusedChild->canBeginDrag(_pressBtn, cpos))
		{
			_focusedChild->_dragStart = cpos;
			_focusedChild->_dragging = true;
			_DraggingControl = _focusedChild;
			return;
		}
		else _focusedChild->_dragPossible = false;
	}

	_hoveredChild = nullptr;
	bool set = false;
	ANIP_ui_LoopOverChildren(child)
	{
		vec2 cpos = pos - child->_pos - _margin
			+ (child->fixedPosition ? vec2{0} : scroll());
		if (!set && child->isPointInside(cpos))
		{
			_hoveredChild = child;
			if (!child->_hover)
				child->processMouseEnter();
			
			child->processMouseMove(cpos);
			set = true;
		}
		else if (child->_hover)
			child->processMouseLeave();
	}
}

void ui::Control::processMouseEnter()
{
	_hover = true;
	if (onMouseEnter) onMouseEnter(*this);
}

void ui::Control::processMouseLeave()
{
	_hover = false;
	if (onMouseLeave) onMouseLeave(*this);
	if (_hoveredChild != nullptr)
		_hoveredChild->processMouseLeave();
}

void ui::Control::processMouseButton(MouseButton btn, ButtonAction action)
{
	//wcout << L"mouseButton ["s + utf8s2ws(typeid(*this).name()) + L"] ["s + _text + L"]\n"s;

	if (onMouseButton) onMouseButton(*this, btn, action);
	if (action == ButtonAction::Down)
	{
		_press = true;
		_pressBtn = btn;
		_focusedChild = nullptr;
		bool set = false;

		ANIP_ui_LoopOverChildren(child)
		{
			if (!set && child->_hover)
			{
				_focusedChild = child;
				if (!child->_focus)
					child->processFocus();
					child->_dragPossible = true;
				child->processMouseButton(btn, action);
				set = true;
			}
			else if (child->_focus)
				child->processDefocus();
		}
	}
	else if (action == ButtonAction::Up)
	{
		_press = false;

		// it's possible that the focus changed after the mouse being
		// pressed and before it being released, so _focusedChild can't
		// directly tell us which child to issue the event of the mouse
		// being released.
		ANIP_ui_LoopOverChildren(child)
		{
			if (child->_press)
			{
				child->processMouseButton(btn, action);

				child->_dragPossible = false;
				if (child->_dragging)
				{
					assert(_DraggingControl == child);
					child->_dragging = false;
					_DraggingControl = nullptr;
					child->processEndDrag();
				}
			}
		}
	}
}

void ui::Control::focus()
{
	assert(_parent != nullptr);
	if (!_focus)
	{
		if (_parent->_focusedChild != nullptr)
			_parent->_focusedChild->processDefocus();
		_parent->_focusedChild = this;
		processFocus();
	}
}

void ui::Control::processFocus()
{
	_focus = true;
	if (onFocus) onFocus(*this);
}

void ui::Control::processDefocus()
{
	_focus = false;
	if (onDefocus) onDefocus(*this);
	if (_focusedChild != nullptr)
		_focusedChild->processDefocus();
}

void ui::Control::processParentResize()
{
	//postSignal({ Signal::Type::NeedsReflow });

	if (onParentResize) onParentResize(*this);
}

void ui::Control::processScroll(invec2 pos)
{
	if (_hoveredChild != nullptr)
		_hoveredChild->processScroll(pos);
}

void ui::Control::processKey(Key k, ButtonAction action)
{
	if (onKeyAction) onKeyAction(*this, k, action);
	if (_focusedChild != nullptr)
		_focusedChild->processKey(k, action);
}

void ui::Control::processUnicodeInput(wchar_t ch)
{
	if (_focusedChild != nullptr)
		_focusedChild->processUnicodeInput(ch);
};

void ui::Control::processMarkedDelete()
{
	if (onDeleting) onDeleting(*this);
}

void ui::Control::flow(Flow &f)
{
	if (_parent != nullptr)
	{
		if (layoutOptions.dock != LayoutOptions::DockType::None)
		{
			vec2 pos{}, size = _size;
			switch (layoutOptions.dock)
			{
			case LayoutOptions::DockType::Left:
				pos = f.dockableStart;
				size.y = f.dockableEnd.y - f.dockableStart.y;
				f.dockableStart.x += _size.x;
				break;
			case LayoutOptions::DockType::Right:
				pos = { f.dockableEnd.x - _size.x, f.dockableStart.y };
				size.y = f.dockableEnd.y - f.dockableStart.y;
				f.dockableEnd.x -= _size.x;
				break;
			case LayoutOptions::DockType::Top:
				pos = f.dockableStart;
				size.x = f.dockableEnd.x - f.dockableStart.x;
				f.dockableStart.y += _size.y;
				break;
			case LayoutOptions::DockType::Bottom:
				pos = { f.dockableStart.x, f.dockableEnd.y - _size.y };
				size.x = f.dockableEnd.x - f.dockableStart.x;
				f.dockableEnd.y -= _size.y;
				break;
			case LayoutOptions::DockType::Fill:
				pos = f.dockableStart;
				size = f.dockableEnd - f.dockableStart;
				f.dockableStart = f.dockableEnd;
				break;
			default:
				break;
			}
			size.x = max(size.x, layoutOptions.minSize.x);
			size.y = max(size.y, layoutOptions.minSize.y);
			setPosition(pos);
			setSize(size);
		}
		// So we don't process stretchX/Y here
	}

	flowChildrenDefault();
}

void ui::Control::setPosition(invec2 pos)
{
	postSignal({ Signal::Type::NeedsRedraw });

	_pos = pos;
	if (_parent != nullptr)
	{
		_absPos = _parent->toAbsolutePos(_pos);
		if (fixedPosition) _absPos += _parent->scroll();
	}
	else
		_absPos = pos;

	ANIP_ui_LoopOverChildren(child)
		child->setPosition(child->_pos);
}

void ui::Control::setSize(invec2 size)
{
	if (_size == size) return;
	postSignal({ Signal::Type::NeedsRedraw });

	_size = size;
	scroll.onChanged(); // we need to update scrolling in case it gets out of range

	ANIP_ui_LoopOverChildren(child)
	{
		child->processParentResize();
	}
}

void ui::Control::setInheritFont(const Font& f)
{
	_inheritFont = f;
	ANIP_ui_LoopOverChildren(child)
	{
		if (child->_font.isNull())
		{
			child->setFont(f);
			child->setInheritFont(f);
		}
	}
}

void ui::Control::addChild(Control& child, bool top)
{
	assert(child._parent == nullptr);
	postSignal({ Signal::Type::NeedsReflow });

	child._parent = this;
	if (child._font.isNull() && !_inheritFont.isNull())
	{
		child.setFont(_inheritFont);
		child.setInheritFont(_inheritFont);
	}

	if (_firstChild == nullptr)
	{
		_firstChild = &child;
		_lastChild = &child;
		child._prev = nullptr;
		child._next = nullptr;
	} else if (top)
	{
		Control* oldFirst = _firstChild;
		_firstChild = &child;
		child._prev = nullptr;
		child._next = oldFirst;
		oldFirst->_prev = &child;
	}
	else
	{
		Control* oldLast = _lastChild;
		_lastChild = &child;
		child._prev = oldLast;
		child._next = nullptr;
		oldLast->_next = &child;
	}
}

void ui::Control::insertChild(Control& child, Control& before)
{
	assert(child._parent == nullptr);
	postSignal({ Signal::Type::NeedsReflow });

	child._parent = this;
	if (child._font.isNull() && !_inheritFont.isNull())
	{
		child.setFont(_inheritFont);
		child.setInheritFont(_inheritFont);
	}

	if (before._prev == nullptr)
	{
		_firstChild = before._prev = &child;
		child._prev = nullptr;
		child._next = &before;
	}
	else
	{
		child._prev = before._prev;
		child._next = &before;
		before._prev->_next = &child;
		before._prev = &child;
	}
}

void ui::Control::detachChild(Control& c)
{
	assert(c._parent == this);

	postSignal({ Signal::Type::NeedsReflow });

	if (_firstChild == &c)
		_firstChild = c._next;
	else
		c._prev->_next = c._next;

	if (_lastChild == &c)
		_lastChild = c._prev;
	else
		c._next->_prev = c._prev;

	if (_focusedChild == &c) _focusedChild = nullptr;
	if (_hoveredChild == &c) _hoveredChild = nullptr;
	if (_dragOverChild == &c) _dragOverChild = nullptr;
	//if (_DraggingControl == &c) _DraggingControl = nullptr;

	c._next = c._prev = nullptr;
	c._parent = nullptr;
}

void ui::Control::replaceChild(Control& child, Control& repl)
{
	assert(child._parent == this);
	assert(repl._parent == nullptr);
	if (&child == &repl) return;
	postSignal({ Signal::Type::NeedsReflow });

	repl._parent = this;
	if (repl._font.isNull() && !_inheritFont.isNull())
	{
		repl.setFont(_inheritFont);
		repl.setInheritFont(_inheritFont);
	}

	repl._prev = child._prev;
	repl._next = child._next;
	if (_firstChild == &child)
		_firstChild = &repl;
	else
		repl._prev->_next = &repl;
	if (_lastChild == &child)
		_lastChild = &repl;
	else
		repl._next->_prev = &repl;

	if (_focusedChild == &child) _focusedChild = nullptr;
	if (_hoveredChild == &child) _hoveredChild = nullptr;
	if (_dragOverChild == &child) _dragOverChild = nullptr;
	//if (_DraggingControl == &child) _DraggingControl = nullptr;

	child._prev = child._next = nullptr;
	child._parent = nullptr;
}

void ui::Control::bringToFront()
{
	postSignal({ Signal::Type::NeedsRedraw });

	assert(_parent != nullptr);
	if (_prev == nullptr) return;
	if (_next == nullptr)
	{
		_prev->_next = nullptr;
		_parent->_lastChild = _prev;
	}
	else
	{
		_prev->_next = _next;
		_next->_prev = _prev;
	}
	Control* oldFirst = _parent->_firstChild;
	_parent->_firstChild = this;
	oldFirst->_prev = this;
	_prev = nullptr;
	_next = oldFirst;
}

void ui::Control::bringToBack()
{
	postSignal({ Signal::Type::NeedsRedraw });

	assert(_parent != nullptr);
	
	if (_next == nullptr) return;
	if (_prev == nullptr)
	{
		_parent->_firstChild = _next;
		_next->_prev = nullptr;
	}
	else
	{
		_prev->_next = _next;
		_next->_prev = _prev;
	}
	Control* oldLast = _parent->_lastChild;
	_parent->_lastChild = this;
	oldLast->_next = this;
	_prev = oldLast;
	_next = nullptr;
}

void ui::Control::_postSignal(const Signal& s)
{
	if (_signals.find(s) != _signals.end())
		return;

	_signals.insert(s);
	if (_parent != nullptr)
		_parent->_postSignal(s);
}

void ui::Control::postSignal(const Signal& s)
{
	if (_signals.find(s) != _signals.end())
		return;
	
	// ANIP_FIXME: should I put this here?
	if (s.type == Signal::Type::UpdateScrollableArea)
	{
		updateMaxScroll();
		return;
	}

	_postSignal(s);
	//cout << typeid(*this).name() << " posted " << (int)s.type << endl;
}

void ui::Control::_terminateSignal(const Signal& s)
{
	if (_signals.find(s) == _signals.end())
		return;

	_signals.erase(s);
	ANIP_ui_LoopOverChildren(child)
		child->_terminateSignal(s);
}

void ui::Control::terminateSignal(const Signal& s)
{
	if (_signals.find(s) == _signals.end())
		return;

	_terminateSignal(s);
	//cout << endl << typeid(*this).name() << " terminated " << (int)s.type << endl << endl;
}

void ui::Control::paintChildren()
{
	Context::addScissor(_absPos, _size);
	vec2 sp, ss;
	Context::getScissor(sp, ss);

	for (Control* child = lastChild(); child != nullptr; child = child->previous())
	{
		Context::setScissor(sp, ss);
		child->paint();
	}
}

void ui::Control::retrieveEvents()
{
	UserEvent ev;
	while (Context::pollEvent(ev))
	{
		switch (ev.type)
		{
		case UserEvent::Type::Resize:
			if (Context::size() == vec2{ 0 })
			{
				// window minimized
				processDefocus();

				// ANIP_FIXME: currently, drawing will proceed after the minimization 
				// if anything called markNeedAnimate; this will generate undefined 
				// graphical results though it won't break anything.
			}
			else if (Context::size() * _UIScaling != _size)
			{
				setSize(Context::size() / _UIScaling);
				processParentResize();
			}
			break;

		case UserEvent::Type::MouseMove:
			if (_DraggingControl != nullptr)
			{
				_DraggingControl->processDrag(
					_DraggingControl->_dragStart, 
					_DraggingControl->toLocalPos(ev.pos / _UIScaling) 
						+ _DraggingControl->_margin);
			}
			processMouseMove(ev.pos / _UIScaling);
			break;

		case UserEvent::Type::MouseAction:
			processMouseButton(ev.mouse.button, ev.mouse.action);
			break;

		case UserEvent::Type::Scroll:
			processScroll(ev.scroll);
			break;

		case UserEvent::Type::Key:
			processKey(ev.key.value, ev.key.action);
			break;

		case UserEvent::Type::UnicodeInput:
			processUnicodeInput(ev.unicode);
			break;

		default:
			// do nothing?
			break;
		}
	}
}

void ui::Control::defaultFlowLayout(ui::Flow& f)
{
	if (f.currentX + _size.x > f.borderRight)
	{
		f.newLine();
	}
	f.lineHeight = max(f.lineHeight, _size.y);
	setPosition({ f.currentX, f.currentY });
	f.currentX += _size.x + r::Flow_Separator;
}

void ui::Control::flowChildrenDefault()
{
	Flow f((Container&)*this); // ANIP_TODO: strange but seems acceptable
	flowChildren(f);
}

void ui::Control::flowChildren(Flow& f)
{
	Control* child = _firstChild;
	while (child != nullptr)
	{
		Control* next = child->_next;
		if (child->_delete)
		{
			detachChild(*child);
			if (_DraggingControl == child)
				_DraggingControl = nullptr;
			delete child;
		}
		child = next;
	}

	ANIP_ui_LoopOverChildren(child)
		child->flow(f);

	updateMaxScroll();
}

void ui::Control::updateMaxScroll()
{
	vec2 maxPos{};
	ANIP_ui_LoopOverChildren(child)
	{
		vec2 max = child->_pos + child->_size;
		if (max.x > maxPos.x) maxPos.x = max.x;
		if (max.y > maxPos.y) maxPos.y = max.y;
	}

	vec2 nsize = _size - _margin - _marginDown;
	vec2 ms{};
	if (maxPos.x > nsize.x) ms.x = maxPos.x - nsize.x;
	if (maxPos.y > nsize.y) ms.y = maxPos.y - nsize.y;
	if (_maxScroll != ms)
	{
		_maxScroll = ms;
		postSignal({ ui::Signal::Type::NeedsRedraw });
	}
}

void ui::Control::mainLoop()
{
	assert(_TopLevel == this);

	setSize(Context::size() / _UIScaling);
	terminateSignal({ Signal::Type::NeedsReflow });

	Context::currentTarget().transformPush(Transform2D({0, 0}, _UIScaling));
	flowChildrenDefault();
	Context::show();

	bool sleep = false;
	while (!Context::windowShouldClose())
	{
		processTopLevelFrame();
		retrieveEvents();
		if (Animation::_isActive())
		{
			postSignal({ Signal::Type::Animation });
			Animation::_update();
			Context::currentTarget().transformPop();
		}
		if (hasSignal({ Signal::Type::NeedsReflow }))
		{
			terminateSignal({ Signal::Type::NeedsReflow });
			flowChildrenDefault();
			sleep = false;
		}
		if (!sleep || hasSignal({ Signal::Type::NeedsRedraw }))
		{
			terminateSignal({ Signal::Type::NeedsRedraw });

			Context::startFrame();
			Context::noScissor();
			Context::clear(Colors::white);
			Context::currentTarget().transformPush(Transform2D({0, 0}, _UIScaling));
			paint();
			Context::currentTarget().transformPop();

#ifndef NDEBUG
			Draw::text({ 0, _size.y - _font.size }, 
				toString(1.0 / Context::dampedDeltaTime()), 
				Colors::black, _font);
#endif
			if (hasSignal({ Signal::Type::Animation }))
			{
				sleep = false;
				terminateSignal({ Signal::Type::Animation });
			}
			else sleep = true;

			Context::update();
			Context::endFrame();
		}
		Context::doEvents(sleep);
	}

	_TopLevel = nullptr;
}